package com.emeraldparser.grammar;

import static com.emeraldparser.util.Functions2.memoized;

import com.emeraldparser.symbol.SymbolClass;
import com.emeraldparser.symbol.SymbolClass.State;
import com.emeraldparser.util.Automaton;
import com.emeraldparser.util.Automatons;
import com.emeraldparser.util.HashCodes;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;

public final class Rule {

	private final SymbolClass leftSymbolClass;
	private final SymbolClass rightSymbolClass;
	private final transient int hashCode;
	private Automaton<SymbolClass.State, SymbolClass> leftAutomaton;
	private Automaton<SymbolClass.State, SymbolClass> rightAutomaton;

	public Rule(SymbolClass leftSymbolClass, SymbolClass rightSymbolClass) {
		this.leftSymbolClass = Preconditions.checkNotNull(leftSymbolClass, "Left symbol class must not be null");
		this.rightSymbolClass = Preconditions.checkNotNull(rightSymbolClass, "Right symbol class must not be null");
		this.hashCode = HashCodes.classSpecificHashCode(Rule.class, leftSymbolClass, rightSymbolClass);
	}

	public static Rule create(SymbolClass leftSymbolClass, SymbolClass rightSymbolClass) {
		return new Rule(leftSymbolClass, rightSymbolClass);
	}

	public SymbolClass getLeftSymbolClass() {
		return leftSymbolClass;
	}

	public SymbolClass getRightSymbolClass() {
		return rightSymbolClass;
	}

	public Automaton<State, SymbolClass> getLeftAutomaton() {
		if (leftAutomaton == null) {
			leftAutomaton = Automatons.transformVertices(
					leftSymbolClass.toAutomaton(),
					memoized(
							new Function<State, State>() {
								@Override public State apply(State state) {
									return new LeftState(Rule.this, state);
								}
							}
						)
				);
		}
		
		return leftAutomaton;
	}

	public Automaton<SymbolClass.State, SymbolClass> getRightAutomaton() {
		if (rightAutomaton == null) {
			rightAutomaton = Automatons.transformVertices(
					rightSymbolClass.toAutomaton(),
					memoized(
							new Function<State, State>() {
								@Override public State apply(State state) {
									return new RightState(Rule.this, state);
								}
							}
						)
				);
		}
		
		return rightAutomaton;
	}

	@Override
	public String toString() {
		return leftSymbolClass + " = " + rightSymbolClass;
	}

	@Override
	public int hashCode() {
		return hashCode;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		
		if (obj == null) {
			return false;
		}
		
		if (getClass() != obj.getClass()) {
			return false;
		}
		
		Rule other = (Rule) obj;
		
		return Objects.equal(leftSymbolClass, other.leftSymbolClass)
		    && Objects.equal(rightSymbolClass, other.rightSymbolClass);
	}

}
